/*
 *  Copyright 2015-2016, RenGu Technology (BeiJing) Co., Ltd.
 *  All right reserved.
 *
 *  THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF RENGU TECHNOLOGY (BeiJing) CO., LTD.
 *
 *  THE CONTENTS OF THIS FILE MAY NOT BE DISCLOSED TO THIR PARTIES,
 *  COPIED OR DUPLICATED IN ANY FORM, IN WHOLE OR IN PART,
 *  WITHOUT THE PRIOR WRITTEN PERMISSION OF RENGU TECHNOLOGY (BeiJing) CO., LTD.
 *
 *  AcctOpt
 *
 *  Edit History:
 *      lizhangtao Created on 2020年05月13日
 *
 */
package cc.rengu.igas.acct.core.service.realize;

import cc.rengu.igas.acct.common.constant.AcctCommonConstant;
import cc.rengu.igas.acct.common.dao.AcctDetailMapper;
import cc.rengu.igas.acct.common.dao.AcctInfoMapper;
import cc.rengu.igas.acct.common.dao.MapperCommon;
import cc.rengu.igas.acct.common.dao.impl.AcctDetailMapperImpl;
import cc.rengu.igas.acct.common.dao.impl.AcctInfoMapperImpl;
import cc.rengu.igas.acct.common.entity.AcctDetail;
import cc.rengu.igas.acct.common.entity.AcctInfo;
import cc.rengu.igas.acct.common.enums.AcctRespCodeEnum;
import cc.rengu.igas.acct.common.enums.AcctStatusEnum;
import cc.rengu.igas.acct.common.enums.AcctTransStatusEnum;
import cc.rengu.igas.acct.common.util.CheckAcct3o1;
import cc.rengu.igas.acct.common.util.CheckCutDayUtil;
import cc.rengu.igas.acct.common.util.PackAcctDetail;
import cc.rengu.igas.acct.common.util.QueryAcct3o1;
import cc.rengu.oltp.service.model.BizException;
import cc.rengu.oltp.utility.util.AmountUtil;
import cc.rengu.oltp.utility.util.DbsUtil;
import cc.rengu.utility.base.StringUtil;
import cc.rengu.utility.log.RgLogger;

/**
 * 账户操作类，所有的账户操作必须通过该类实现
 *
 * @author lizhangtao
 * @version 1.0
 * @date 2020/5/13
 */
public class AcctOpt {

    /**
     * 冻结操作，不带事物
     * 1. 检查交易金额，冻结条件等；检查日切，日切状态为日切结束
     * 2. 开启事物
     * 3. 锁定账户，根据acctNo | customerId | (userType & userId)
     * 4. 检查账户状态、账户可用余额
     * 5. 计算冻结后账户余额、可用余额、冻结余额并更新账户
     * 6. 插入记账明细表
     * 7. 再一次检查日切，如果日切状态是否变更为日切开始或者账务日期与原日期不一致则拒绝接交易回滚并结束事物
     * 8. 成功提交并结束事物
     *
     * @param txnDate    交易日期
     * @param txnTime    交易时间
     * @param txnNum     交易码
     * @param msgSrcId   请求源id
     * @param instId     法人机构号
     * @param transAmt   交易金额，必须大于0
     * @param acctNo     账户
     * @param customerId 客户号
     * @param userType   外部用户类型
     * @param userId     外部用户id
     * @param traceNo    流水号
     * @param summary    摘要
     * @param rglog      日志句柄
     * @param acctDate   账户日期
     * @param dbsUtil    数据库操作句柄，由外部控制事物
     * @return AcctDetail 成功返回登记的账户交易明细实体
     * @throws Exception 失败抛出异常
     */
    public AcctDetail freeze(String txnDate, String txnTime, String txnNum, String msgSrcId, String instId,
                             String traceNo, String transAmt, String acctNo, String customerId, String userType,
                             String userId, String summary, RgLogger rglog, String acctDate, DbsUtil dbsUtil) throws Exception {
        return freeze(txnDate, txnTime, txnNum, msgSrcId, instId, traceNo, transAmt, acctNo, customerId, userType,
                userId, summary, rglog, acctDate, dbsUtil, 0);
    }

    /**
     * 冻结操作，不带事物,用于多商户预授权
     * 1. 检查交易金额，冻结条件等；检查日切，日切状态为日切结束
     * 2. 开启事物
     * 3. 锁定账户，根据acctNo | customerId | (userType & userId)
     * 4. 检查账户状态、账户可用余额
     * 5. 计算冻结后账户余额、可用余额、冻结余额并更新账户
     * 6. 插入记账明细表
     * 7. 再一次检查日切，如果日切状态是否变更为日切开始或者账务日期与原日期不一致则拒绝接交易回滚并结束事物
     * 8. 成功提交并结束事物
     *
     * @param txnDate    交易日期
     * @param txnTime    交易时间
     * @param txnNum     交易码
     * @param msgSrcId   请求源id
     * @param instId     法人机构号
     * @param transAmt   交易金额，必须大于0
     * @param acctNo     账户
     * @param customerId 客户号
     * @param userType   外部用户类型
     * @param userId     外部用户id
     * @param traceNo    流水号
     * @param summary    摘要
     * @param rglog      日志句柄
     * @param acctDate   账户日期
     * @param dbsUtil    数据库操作句柄，由外部控制事物
     * @param total      多商户明细总数
     * @return AcctDetail 成功返回登记的账户交易明细实体
     * @throws Exception 失败抛出异常
     */
    public AcctDetail freeze(String txnDate, String txnTime, String txnNum, String msgSrcId, String instId,
                             String traceNo, String transAmt, String acctNo, String customerId, String userType,
                             String userId, String summary, RgLogger rglog, String acctDate, DbsUtil dbsUtil, int total) throws Exception {
        AcctDetail acctDetail;
        //3. 锁定账户，根据acctNo | customerId | (userType & userId)
        AcctInfo acctInfo = QueryAcct3o1.lockAcctInfo(instId, acctNo, customerId, userType, userId, dbsUtil, rglog);

        //4. 检查账户状态、账户可用余额
        if (!AcctStatusEnum.NORMAL.getAcctStatus().equals(acctInfo.getAcctStatus())) {
            rglog.error("当前账户状态<{}>不允许冻结！", acctInfo.getAcctStatus());
            throw new BizException(AcctRespCodeEnum.STATUS_NOT_ALLOW_ERROR.getRespCode(),
                    AcctRespCodeEnum.STATUS_NOT_ALLOW_ERROR.getRespDesc());
        }
        //扣减账户余额到冻结金额 (==1 可用余额大于交易金额)  (==0 可用余额等于交易金额)
        if (AmountUtil.compare(acctInfo.getAvailableBalance(), transAmt) < 0) {
            rglog.error("账户<{}>可用余额<{}>不足！交易金额<{}>", acctInfo.getAcctNo(), acctInfo.getAvailableBalance(), transAmt);
            throw new BizException(AcctRespCodeEnum.BALBNCE_NOT_ENOUGH_ERROR.getRespCode(),
                    AcctRespCodeEnum.BALBNCE_NOT_ENOUGH_ERROR.getRespDesc());
        }
        //检查账户余额=可用余额+冻结金额
        if (0 != AmountUtil.compare(acctInfo.getAcctBalance(),
                AmountUtil.addition(acctInfo.getAvailableBalance(), acctInfo.getFreezeBalance()))) {
            //检查账户余额≠可用余额+冻结金额
            throw new BizException(AcctRespCodeEnum.STATUS_NOT_ALLOW_ERROR.getRespCode(),
                    AcctRespCodeEnum.STATUS_NOT_ALLOW_ERROR.getRespDesc());
        }
        //5. 计算冻结后账户余额、可用余额、冻结余额并更新账户
        //账户余额不变
        //可用余额扣减
        String availableBalance = AmountUtil.subtraction(acctInfo.getAvailableBalance(), transAmt);
        //冻结余额增加
        String freezeBalance = AmountUtil.addition(acctInfo.getFreezeBalance(), transAmt);
        AcctInfoMapper acctInfoMapper = new AcctInfoMapperImpl();
        int iRetCode = acctInfoMapper.freezeAcctInfoByAcctNo(instId, acctInfo.getAcctNo(), availableBalance, freezeBalance, dbsUtil);
        if (Integer.MIN_VALUE == iRetCode) {
            throw new BizException(AcctRespCodeEnum.DB_EXCEPTION.getRespCode(),
                    AcctRespCodeEnum.DB_EXCEPTION.getRespDesc());
        } else if (0 != iRetCode) {
            throw new BizException(AcctRespCodeEnum.DB_ERROR.getRespCode(),
                    AcctRespCodeEnum.DB_ERROR.getRespDesc());
        } else {
            //6. 插入记账明细表 由于账户余额实际没有发生变化因此记账类型为冻结
            AcctDetailMapper acctDetailMapper = new AcctDetailMapperImpl();
            acctDetail = PackAcctDetail.packAcctDetail(txnDate, txnTime, txnNum, msgSrcId, traceNo, summary, transAmt, acctInfo,
                    acctDate, acctInfo.getAcctBalance(), availableBalance, freezeBalance, AcctCommonConstant.FREEZE, dbsUtil, total);
            iRetCode = acctDetailMapper.insert2AcctDetail(acctDetail, dbsUtil);
            if (Integer.MIN_VALUE == iRetCode) {
                throw new BizException(AcctRespCodeEnum.DB_EXCEPTION.getRespCode(),
                        AcctRespCodeEnum.DB_EXCEPTION.getRespDesc());
            } else if (0 != iRetCode) {
                throw new BizException(AcctRespCodeEnum.DB_ERROR.getRespCode(),
                        AcctRespCodeEnum.DB_ERROR.getRespDesc());
            } else {
                return acctDetail;
            }
        }
    }

    /**
     * 冻结操作 带事物
     * 1. 检查交易金额，冻结条件等；检查日切，日切状态为日切结束
     * 2. 开启事物
     * 8. 成功提交并结束事物,失败回滚事务
     *
     * @param txnDate    交易日期
     * @param txnTime    交易时间
     * @param txnNum     交易码
     * @param msgSrcId   请求源id
     * @param instId     法人机构号
     * @param transAmt   交易金额，必须大于0
     * @param acctNo     账户
     * @param customerId 客户号
     * @param userType   外部用户类型
     * @param userId     外部用户id
     * @param traceNo    流水号
     * @param summary    摘要
     * @param rglog      日志句柄
     * @return AcctDetail 成功返回登记的账户交易明细实体
     * @throws Exception 失败抛出异常
     */
    public AcctDetail freeze(String txnDate, String txnTime, String txnNum, String msgSrcId, String instId,
                             String traceNo, String transAmt, String acctNo, String customerId,
                             String userType, String userId, String summary, RgLogger rglog) throws Exception {
        if (StringUtil.isNullorEmpty(traceNo) || StringUtil.isNullorEmpty(instId)) {
            rglog.error("冻结失败:instId、traceNo必须上送");
            throw new BizException(AcctRespCodeEnum.MSG_VALID_CHECK_ERROR.getRespCode(),
                    AcctRespCodeEnum.MSG_VALID_CHECK_ERROR.getRespDesc());
        }
        CheckAcct3o1.checkReq(acctNo, customerId, userType, userId, transAmt, rglog);

        //2. 开启事物
        AcctDetail acctDetail;
        String dbPoolName = MapperCommon.getDbPoolName();
        DbsUtil dbsUtil = new DbsUtil(dbPoolName);
        dbsUtil.dbsBeginTransaction();
        try {
            //检查记账日期
            String acctDate = CheckCutDayUtil.check(instId, dbsUtil);
            acctDetail = freeze(txnDate, txnTime, txnNum, msgSrcId, instId, traceNo, transAmt, acctNo, customerId,
                    userType, userId, summary, rglog, acctDate, dbsUtil);
            //7. 再一次检查日切，如果日切状态是否变更为日切开始或者账务日期与原日期不一致则拒绝交易回滚并结束事物
            acctDateConfirm(instId, acctDate, dbsUtil, rglog);
        } catch (Exception e) {
            rglog.error(StringUtil.ExceptionToString(e));
            dbsUtil.dbsEndTransaction(false);
            throw e;
        }
        //8. 成功提交并结束事物
        dbsUtil.dbsEndTransaction(true);
        return acctDetail;
    }

    /**
     * 冻结撤销操作
     * 1. 检查日切，日切状态为日切结束
     * 2. 开启事物
     * 3. 锁定账户，根据acctNo | customerId | (userType & userId)
     * 4. 检查原交易，冻结条件等；检查账户状态、账户冻结余额
     * 5. 计算冻结撤销后账户余额、可用余额、冻结余额并更新账户
     * 6. 插入记账明细表
     * 7. 再一次检查日切，如果日切状态是否变更为日切开始或者账务日期与原日期不一致则拒绝接交易回滚并结束事物
     * 8. 更新原交易为已经撤销
     * 9. 成功提交并结束事物
     *
     * @param txnDate          交易日期
     * @param txnTime          交易时间
     * @param txnNum           交易码
     * @param msgSrcId         请求源id
     * @param instId           法人机构号
     * @param traceNo          流水号
     * @param freezeAcctSeqNum 原冻结流水号
     * @param transAmt         交易金额，必须等于原交易金额
     * @param acctNo           账户 (账户 | 客户号 | 外部用户类型&外部用户id)必须至少三选一，若上送则检查是否与原交易一致
     * @param customerId       客户号
     * @param userType         外部用户类型
     * @param userId           外部用户id
     * @param summary          摘要
     * @param rglog            日志句柄
     * @param acctDate         账户日期
     * @param dbsUtil          数据库操作句柄，由外部控制事物
     * @return AcctDetail 成功返回登记的账户交易明细实体
     * @throws Exception 失败抛出异常
     */
    public AcctDetail freezeCancel(String txnDate, String txnTime, String txnNum, String msgSrcId,
                                   String instId, String traceNo, String freezeAcctSeqNum, String transAmt,
                                   String acctNo, String customerId, String userType, String userId,
                                   String summary, RgLogger rglog, String acctDate, DbsUtil dbsUtil) throws BizException {
        return freezeCancel(txnDate, txnTime, txnNum, msgSrcId, instId, traceNo, freezeAcctSeqNum, transAmt,
                acctNo, customerId, userType, userId, summary, rglog, acctDate, dbsUtil, 0);
    }

    /**
     * 冻结撤销操作
     * 1. 检查日切，日切状态为日切结束
     * 2. 开启事物
     * 3. 锁定账户，根据acctNo | customerId | (userType & userId)
     * 4. 检查原交易，冻结条件等；检查账户状态、账户冻结余额
     * 5. 计算冻结撤销后账户余额、可用余额、冻结余额并更新账户
     * 6. 插入记账明细表
     * 7. 再一次检查日切，如果日切状态是否变更为日切开始或者账务日期与原日期不一致则拒绝接交易回滚并结束事物
     * 8. 更新原交易为已经撤销
     * 9. 成功提交并结束事物
     *
     * @param txnDate          交易日期
     * @param txnTime          交易时间
     * @param txnNum           交易码
     * @param msgSrcId         请求源id
     * @param instId           法人机构号
     * @param traceNo          流水号
     * @param freezeAcctSeqNum 原冻结流水号
     * @param transAmt         交易金额，必须等于原交易金额
     * @param acctNo           账户 (账户 | 客户号 | 外部用户类型&外部用户id)必须至少三选一，若上送则检查是否与原交易一致
     * @param customerId       客户号
     * @param userType         外部用户类型
     * @param userId           外部用户id
     * @param summary          摘要
     * @param rglog            日志句柄
     * @param acctDate         账户日期
     * @param dbsUtil          数据库操作句柄，由外部控制事物
     * @param total            多商户预授权的明细总数
     * @return AcctDetail 成功返回登记的账户交易明细实体
     * @throws Exception 失败抛出异常
     */
    public AcctDetail freezeCancel(String txnDate, String txnTime, String txnNum, String msgSrcId,
                                   String instId, String traceNo, String freezeAcctSeqNum, String transAmt,
                                   String acctNo, String customerId, String userType, String userId,
                                   String summary, RgLogger rglog, String acctDate, DbsUtil dbsUtil, int total) throws BizException {
        if (StringUtil.isNullorEmpty(traceNo) || StringUtil.isNullorEmpty(instId) || StringUtil.isNullorEmpty(freezeAcctSeqNum)) {
            rglog.error("冻结撤销失败:instId、traceNo、freezeAcctSeqNum必须上送");
            throw new BizException(AcctRespCodeEnum.MSG_VALID_CHECK_ERROR.getRespCode(), AcctRespCodeEnum.MSG_VALID_CHECK_ERROR.getRespDesc());
        }
        //交易金额一定要大于0; (==1交易金额大于0) (==0交易金额等于0) (==-1交易金额小于0)
        if (1 != AmountUtil.compare(transAmt, AcctCommonConstant.ZERO_AMT)) {
            rglog.error("交易金额非法，小于等于0");
            throw new BizException(AcctRespCodeEnum.MSG_VALID_CHECK_ERROR.getRespCode(), AcctRespCodeEnum.MSG_VALID_CHECK_ERROR.getRespDesc());
        }
        //2. 开启事物
        AcctDetail acctDetail;
        //3. 锁定账户，根据acctNo | customerId | (userType & userId)
        AcctInfo acctInfo = QueryAcct3o1.lockAcctInfo(instId, acctNo, customerId, userType, userId, dbsUtil, rglog);
        //检查原交易
        AcctDetailMapper acctDetailMapper = new AcctDetailMapperImpl();
        AcctDetail acctDetailOrig = acctDetailMapper.selectAcctDetailByAcctSeqNum(instId, freezeAcctSeqNum);
        if (null == acctDetailOrig) {
            rglog.error("机构<{}>记账编号<{}>", instId, freezeAcctSeqNum);
            throw new BizException(AcctRespCodeEnum.ORIG_TRANS_NOT_EXIST_ERROR.getRespCode(), AcctRespCodeEnum.ORIG_TRANS_NOT_EXIST_ERROR.getRespDesc());
        }
        //原交易状态为成功可撤销
        if (!AcctTransStatusEnum.SUCC.getStatus().equals(acctDetailOrig.getAcctStatus())) {
            throw new BizException(AcctRespCodeEnum.ORIG_TRANS_NOT_EXIST_ERROR.getRespCode(), AcctRespCodeEnum.ORIG_TRANS_NOT_EXIST_ERROR.getRespDesc());
        }
        //remark为空检查通过
        if (!StringUtil.isNullorEmpty(acctDetailOrig.getRemark1())) {
            //remark为CANCELED表示已经撤销不可重复撤销
            if (AcctCommonConstant.CANCELED.equals(acctDetailOrig.getRemark1())) {
                throw new BizException(AcctRespCodeEnum.ORIG_TRANS_CANCELED_ERROR.getRespCode(), AcctRespCodeEnum.ORIG_TRANS_CANCELED_ERROR.getRespDesc());
            }
            //remark为COMPELETED表示已经预授权完成不可撤销
            if (AcctCommonConstant.COMPLETED.equals(acctDetailOrig.getRemark1())) {
                throw new BizException(AcctRespCodeEnum.ORIG_TRANS_COMPLETE_ERROR.getRespCode(), AcctRespCodeEnum.ORIG_TRANS_COMPLETE_ERROR.getRespDesc());
            }
        }
        CheckAcct3o1.origTrans(acctNo, customerId, userType, userId, transAmt, acctDetailOrig, rglog);
        //4. 检查账户状态、账户冻结可用余额
        //检查账户状态
        if (!AcctStatusEnum.NORMAL.getAcctStatus().equals(acctInfo.getAcctStatus())) {
            rglog.error("当前账户状态<{}>不允许预授权撤销！", acctInfo.getAcctStatus());
            throw new BizException(AcctRespCodeEnum.STATUS_NOT_ALLOW_ERROR.getRespCode(), AcctRespCodeEnum.STATUS_NOT_ALLOW_ERROR.getRespDesc());
        }
        if (AmountUtil.compare(acctInfo.getFreezeBalance(), transAmt) < 0) {
            //一般情况下不会出现冻结余额不足，若出现则可能存在已经被解冻，但是预授权状态没有更新的交易
            rglog.error("冻结余额不足！");
            throw new BizException(AcctRespCodeEnum.BALBNCE_NOT_ENOUGH_ERROR.getRespCode(), AcctRespCodeEnum.BALBNCE_NOT_ENOUGH_ERROR.getRespDesc());
        }
        //检查账户余额=可用余额+冻结金额
        if (0 != AmountUtil.compare(acctInfo.getAcctBalance(), AmountUtil.addition(acctInfo.getAvailableBalance(), acctInfo.getFreezeBalance()))) {
            //检查账户余额≠可用余额+冻结金额
            throw new BizException(AcctRespCodeEnum.STATUS_NOT_ALLOW_ERROR.getRespCode(), AcctRespCodeEnum.STATUS_NOT_ALLOW_ERROR.getRespDesc());
        }
        //5. 计算冻结撤销后账户余额、可用余额、冻结余额并更新账户
        //余额不变 可用余额增加 冻结余额扣减
        String availableBalance = AmountUtil.addition(acctInfo.getAvailableBalance(), transAmt);
        String freezeBalance = AmountUtil.subtraction(acctInfo.getFreezeBalance(), transAmt);
        AcctInfoMapper acctInfoMapper = new AcctInfoMapperImpl();
        int iRetCode = acctInfoMapper.freezeAcctInfoByAcctNo(instId, acctInfo.getAcctNo(), availableBalance, freezeBalance, dbsUtil);
        if (Integer.MIN_VALUE == iRetCode) {
            throw new BizException(AcctRespCodeEnum.DB_EXCEPTION.getRespCode(), AcctRespCodeEnum.DB_EXCEPTION.getRespDesc());
        } else if (0 != iRetCode) {
            throw new BizException(AcctRespCodeEnum.DB_ERROR.getRespCode(), AcctRespCodeEnum.DB_ERROR.getRespDesc());
        } else {
            //6. 插入记账明细表，由于账户余额实际没有发生变化因此记账类型为冻结
            acctDetail = PackAcctDetail.packAcctDetail(txnDate, txnTime, txnNum, msgSrcId, traceNo, summary, transAmt,
                    acctInfo, acctDate, acctInfo.getAcctBalance(), availableBalance, freezeBalance, AcctCommonConstant.UNFREEZE,
                    acctDetailOrig.getFrontSeqNum(), acctDetailOrig.getAcctSeqNum(), dbsUtil, total);
            iRetCode = acctDetailMapper.insert2AcctDetail(acctDetail, dbsUtil);
            if (Integer.MIN_VALUE == iRetCode) {
                throw new BizException(AcctRespCodeEnum.DB_EXCEPTION.getRespCode(), AcctRespCodeEnum.DB_EXCEPTION.getRespDesc());
            } else if (0 != iRetCode) {
                throw new BizException(AcctRespCodeEnum.DB_ERROR.getRespCode(), AcctRespCodeEnum.DB_ERROR.getRespDesc());
            } else {
                //8. 更新原记账明细状态为已撤销，记账明细撤销状态更新在remark1字段，值为"CANCELED"
                acctDetailMapper.updOrigCanceledByAcctSeqNum(instId, freezeAcctSeqNum, acctDetail.getFrontSeqNum(), acctDetail.getAcctSeqNum(), dbsUtil);
            }
        }
        return acctDetail;
    }

    /**
     * 冻结撤销操作， 带事物
     * 1. 检查日切，日切状态为日切结束
     * 2. 开启事物
     * 3. 锁定账户，根据acctNo | customerId | (userType & userId)
     * 4. 检查原交易，冻结条件等；检查账户状态、账户冻结余额
     * 5. 计算冻结撤销后账户余额、可用余额、冻结余额并更新账户
     * 6. 插入记账明细表
     * 7. 再一次检查日切，如果日切状态是否变更为日切开始或者账务日期与原日期不一致则拒绝接交易回滚并结束事物
     * 8. 更新原交易为已经撤销
     * 9. 成功提交并结束事物
     *
     * @param txnDate          交易日期
     * @param txnTime          交易时间
     * @param txnNum           交易码
     * @param msgSrcId         请求源id
     * @param instId           法人机构号
     * @param traceNo          流水号
     * @param freezeAcctSeqNum 原冻结流水号
     * @param transAmt         交易金额，必须等于原交易金额
     * @param acctNo           账户 (账户 | 客户号 | 外部用户类型&外部用户id)必须至少三选一，若上送则检查是否与原交易一致
     * @param customerId       客户号
     * @param userType         外部用户类型
     * @param userId           外部用户id
     * @param summary          摘要
     * @param rglog            日志句柄
     * @return AcctDetail 成功返回登记的账户交易明细实体
     * @throws Exception 失败抛出异常
     */
    public AcctDetail freezeCancel(String txnDate, String txnTime, String txnNum, String msgSrcId,
                                   String instId, String traceNo, String freezeAcctSeqNum, String transAmt,
                                   String acctNo, String customerId, String userType, String userId,
                                   String summary, RgLogger rglog) throws Exception {
        CheckAcct3o1.checkReq(acctNo, customerId, userType, userId, transAmt, rglog);
        //2. 开启事物
        AcctDetail acctDetail;
        String dbPoolName = MapperCommon.getDbPoolName();
        DbsUtil dbsUtil = new DbsUtil(dbPoolName);
        dbsUtil.dbsBeginTransaction();
        try {
            //检查记账日期
            String acctDate = CheckCutDayUtil.check(instId, dbsUtil);
            acctDetail = freezeCancel(txnDate, txnTime, txnNum, msgSrcId, instId, traceNo, freezeAcctSeqNum, transAmt,
                    acctNo, customerId, userType, userId, summary, rglog, acctDate, dbsUtil);
            //7. 再一次检查日切，如果日切状态是否变更为日切开始或者账务日期与原日期不一致则拒绝接交易回滚并结束事物
            acctDateConfirm(instId, acctDate, dbsUtil, rglog);
        } catch (Exception e) {
            rglog.error(StringUtil.ExceptionToString(e));
            dbsUtil.dbsEndTransaction(false);
            throw e;
        }
        //9. 成功提交并结束事物
        dbsUtil.dbsEndTransaction(true);
        return acctDetail;
    }

    /**
     * 解冻并借记操作 单笔带事物
     * 1. 检查原交易，冻结条件等；检查日切，日切状态为日切结束
     * 2. 开启事物
     * 3. 锁定账户，根据acctNo | customerId | (userType & userId)
     * 4. 检查账户状态、账户冻结余额
     * 5. 计算解冻后冻结余额并更新账户，此时不更新其他的余额
     * 6. 插入记账明细表
     * 7. 再一次检查日切，如果日切状态是否变更为日切开始或者账务日期与原日期不一致则拒绝接交易回滚并结束事物
     * 8. 更新原交易为已经预授权完成
     * 9. 成功提交并结束事物
     *
     * @param txnDate          交易日期
     * @param txnTime          交易时间
     * @param txnNum           交易码
     * @param msgSrcId         请求源id
     * @param instId           法人机构号
     * @param traceNo          流水号
     * @param freezeAcctSeqNum 原冻结流水号
     * @param transAmt         交易金额，必须等于原交易金额
     * @param acctNo           账户 (账户 | 客户号 | 外部用户类型&外部用户id)必须至少三选一，若上送则检查是否与原交易一致
     * @param customerId       客户号
     * @param userType         外部用户类型
     * @param userId           外部用户id
     * @param summary          摘要
     * @param rglog            日志句柄
     * @return AcctDetail 成功返回登记的账户交易明细实体
     * @throws Exception 失败抛出异常
     */
    public AcctDetail freezeComplete(String txnDate, String txnTime, String txnNum, String msgSrcId,
                                     String instId, String traceNo, String freezeAcctSeqNum, String transAmt,
                                     String acctNo, String customerId, String userType, String userId,
                                     String summary, RgLogger rglog) throws Exception {
        if (StringUtil.isNullorEmpty(traceNo) || StringUtil.isNullorEmpty(instId) ||
                StringUtil.isNullorEmpty(freezeAcctSeqNum)) {
            rglog.error("解冻并借记失败:instId、traceNo、freezeAcctSeqNum必须上送");
            throw new BizException(AcctRespCodeEnum.MSG_VALID_CHECK_ERROR.getRespCode(), AcctRespCodeEnum.MSG_VALID_CHECK_ERROR.getRespDesc());
        }
        //2. 开启事物
        AcctDetail acctDetail;
        String dbPoolName = MapperCommon.getDbPoolName();
        DbsUtil dbsUtil = new DbsUtil(dbPoolName);
        dbsUtil.dbsBeginTransaction();
        try {
            //检查记账日期
            String acctDate = CheckCutDayUtil.check(instId, dbsUtil);
            acctDetail = freezeComplete(txnDate, txnTime, txnNum, msgSrcId, instId, traceNo, freezeAcctSeqNum, transAmt,
                    acctNo, customerId, userType, userId, summary, rglog, acctDate, dbsUtil);
            //7. 再一次检查日切，如果日切状态是否变更为日切开始或者账务日期与原日期不一致则拒绝接交易回滚并结束事物
            acctDateConfirm(instId, acctDate, dbsUtil, rglog);
        } catch (Exception e) {
            rglog.error(StringUtil.ExceptionToString(e));
            dbsUtil.dbsEndTransaction(false);
            throw e;
        }
        //9. 成功提交并结束事物
        dbsUtil.dbsEndTransaction(true);
        return acctDetail;
    }

    /**
     * 解冻并借记操作 不带事物
     * 1. 检查原交易，冻结条件等；检查日切，日切状态为日切结束
     * 2. 开启事物
     * 3. 锁定账户，根据acctNo | customerId | (userType & userId)
     * 4. 检查账户状态、账户冻结余额
     * 5. 计算解冻后冻结余额并更新账户，此时不更新其他的余额
     * 6. 插入记账明细表
     * 7. 再一次检查日切，如果日切状态是否变更为日切开始或者账务日期与原日期不一致则拒绝接交易回滚并结束事物
     * 8. 更新原交易为已经预授权完成
     * 9. 成功提交并结束事物
     *
     * @param txnDate          交易日期
     * @param txnTime          交易时间
     * @param txnNum           交易码
     * @param msgSrcId         请求源id
     * @param instId           法人机构号
     * @param traceNo          流水号
     * @param freezeAcctSeqNum 原冻结流水号
     * @param transAmt         交易金额，必须等于原交易金额
     * @param acctNo           账户 (账户 | 客户号 | 外部用户类型&外部用户id)必须至少三选一，若上送则检查是否与原交易一致
     * @param customerId       客户号
     * @param userType         外部用户类型
     * @param userId           外部用户id
     * @param summary          摘要
     * @param rglog            日志句柄
     * @param acctDate         账户日期
     * @param dbsUtil          数据库操作句柄，由外部控制事物
     * @param total            预授权明细总数
     * @return AcctDetail 成功返回登记的账户交易明细实体
     * @throws Exception 失败抛出异常
     */
    public AcctDetail freezeComplete(String txnDate, String txnTime, String txnNum, String msgSrcId,
                                     String instId, String traceNo, String freezeAcctSeqNum, String transAmt,
                                     String acctNo, String customerId, String userType, String userId,
                                     String summary, RgLogger rglog, String acctDate, DbsUtil dbsUtil, int total) throws Exception {
        CheckAcct3o1.checkReq(acctNo, customerId, userType, userId, transAmt, rglog);
        //交易金额一定要大于0; (==1交易金额大于0) (==0交易金额等于0) (==-1交易金额小于0)
        if (1 != AmountUtil.compare(transAmt, AcctCommonConstant.ZERO_AMT)) {
            rglog.error("解冻并借记失败:交易金额非法，小于等于0");
            throw new BizException(AcctRespCodeEnum.MSG_VALID_CHECK_ERROR.getRespCode(), AcctRespCodeEnum.MSG_VALID_CHECK_ERROR.getRespDesc());
        }
        //2. 开启事物
        AcctDetail acctDetail;
        //3. 锁定账户，根据acctNo | customerId | (userType & userId)
        AcctInfo acctInfo = QueryAcct3o1.lockAcctInfo(instId, acctNo, customerId, userType, userId, dbsUtil, rglog);
        //4. 检查账户状态、账户冻结可用余额
        //检查原交易
        AcctDetailMapper acctDetailMapper = new AcctDetailMapperImpl();
        AcctDetail acctDetailOrig = acctDetailMapper.selectAcctDetailByAcctSeqNum(instId, freezeAcctSeqNum);
        if (null == acctDetailOrig) {
            throw new BizException(AcctRespCodeEnum.ORIG_TRANS_NOT_EXIST_ERROR.getRespCode(), AcctRespCodeEnum.ORIG_TRANS_NOT_EXIST_ERROR.getRespDesc());
        }
        //原交易状态为成功可完成
        if (!AcctTransStatusEnum.SUCC.getStatus().equals(acctDetailOrig.getAcctStatus())) {
            throw new BizException(AcctRespCodeEnum.ORIG_TRANS_NOT_EXIST_ERROR.getRespCode(), AcctRespCodeEnum.ORIG_TRANS_NOT_EXIST_ERROR.getRespDesc());
        }
        //remark为空检查通过
        if (!StringUtil.isNullorEmpty(acctDetailOrig.getRemark1())) {
            //remark为CANCELED表示已经撤销不可完成
            if (AcctCommonConstant.CANCELED.equals(acctDetailOrig.getRemark1())) {
                throw new BizException(AcctRespCodeEnum.ORIG_TRANS_CANCELED_ERROR.getRespCode(), AcctRespCodeEnum.ORIG_TRANS_CANCELED_ERROR.getRespDesc());
            }
            //remark为COMPELETED表示已经预授权完成不可重复完成
            if (AcctCommonConstant.COMPLETED.equals(acctDetailOrig.getRemark1())) {
                throw new BizException(AcctRespCodeEnum.ORIG_TRANS_COMPLETE_ERROR.getRespCode(), AcctRespCodeEnum.ORIG_TRANS_COMPLETE_ERROR.getRespDesc());
            }
        }
        CheckAcct3o1.origTrans(acctNo, customerId, userType, userId, transAmt, acctDetailOrig, rglog);
        //检查账户状态
        if (!AcctStatusEnum.NORMAL.getAcctStatus().equals(acctInfo.getAcctStatus())) {
            rglog.error("解冻并借记失败:当前账户状态<{}>不允许预授权撤销！", acctInfo.getAcctStatus());
            throw new BizException(AcctRespCodeEnum.STATUS_NOT_ALLOW_ERROR.getRespCode(), AcctRespCodeEnum.STATUS_NOT_ALLOW_ERROR.getRespDesc());
        }
        if (AmountUtil.compare(acctInfo.getFreezeBalance(), transAmt) < 0) {
            //一般情况下不会出现冻结余额不足，若出现则可能存在已经被解冻，但是预授权状态没有更新的交易
            rglog.error("解冻并借记失败:冻结余额不足！");
            throw new BizException(AcctRespCodeEnum.BALBNCE_NOT_ENOUGH_ERROR.getRespCode(), AcctRespCodeEnum.BALBNCE_NOT_ENOUGH_ERROR.getRespDesc());
        }
        //检查账户余额=可用余额+冻结金额
        if (0 != AmountUtil.compare(acctInfo.getAcctBalance(),
                AmountUtil.addition(acctInfo.getAvailableBalance(), acctInfo.getFreezeBalance()))) {
            //检查账户余额≠可用余额+冻结金额
            throw new BizException(AcctRespCodeEnum.STATUS_NOT_ALLOW_ERROR.getRespCode(), AcctRespCodeEnum.STATUS_NOT_ALLOW_ERROR.getRespDesc());
        }
        //5. 计算冻结撤销后账户余额、可用余额、冻结余额并更新账户
        //余额扣减
        String balance = AmountUtil.subtraction(acctInfo.getAcctBalance(), transAmt);
        //可用余额不变
        //冻结余额扣减
        String freezeBalance = AmountUtil.subtraction(acctInfo.getFreezeBalance(), transAmt);
        AcctInfoMapper acctInfoMapper = new AcctInfoMapperImpl();
        int iRetCode = acctInfoMapper.unfreezeAndDebitByAcctNo(instId, acctInfo.getAcctNo(), balance, freezeBalance, dbsUtil);
        if (Integer.MIN_VALUE == iRetCode) {
            throw new BizException(AcctRespCodeEnum.DB_EXCEPTION.getRespCode(), AcctRespCodeEnum.DB_EXCEPTION.getRespDesc());
        } else if (0 != iRetCode) {
            throw new BizException(AcctRespCodeEnum.DB_ERROR.getRespCode(), AcctRespCodeEnum.DB_ERROR.getRespDesc());
        } else {
            //6. 插入记账明细表，记为借记交易
            acctDetail = PackAcctDetail.packAcctDetail(txnDate, txnTime, txnNum, msgSrcId, traceNo, summary, transAmt,
                    acctInfo, acctDate, balance, acctInfo.getAvailableBalance(), freezeBalance, AcctCommonConstant.DEBIT,
                    acctDetailOrig.getFrontSeqNum(), acctDetailOrig.getAcctSeqNum(), dbsUtil, total);
            iRetCode = acctDetailMapper.insert2AcctDetail(acctDetail, dbsUtil);
            if (Integer.MIN_VALUE == iRetCode) {
                throw new BizException(AcctRespCodeEnum.DB_EXCEPTION.getRespCode(), AcctRespCodeEnum.DB_EXCEPTION.getRespDesc());
            } else if (0 != iRetCode) {
                throw new BizException(AcctRespCodeEnum.DB_ERROR.getRespCode(), AcctRespCodeEnum.DB_ERROR.getRespDesc());
            } else {
                //8. 更新原交易为预授权完成
                acctDetailMapper.updOrigCompletedByAcctSeqNum(instId, freezeAcctSeqNum, acctDetail.getFrontSeqNum(), acctDetail.getAcctSeqNum(), dbsUtil);
            }
        }
        return acctDetail;
    }

    /**
     * 解冻并借记操作 不带事物
     * 1. 检查原交易，冻结条件等；检查日切，日切状态为日切结束
     * 2. 开启事物
     * 3. 锁定账户，根据acctNo | customerId | (userType & userId)
     * 4. 检查账户状态、账户冻结余额
     * 5. 计算解冻后冻结余额并更新账户，此时不更新其他的余额
     * 6. 插入记账明细表
     * 7. 再一次检查日切，如果日切状态是否变更为日切开始或者账务日期与原日期不一致则拒绝接交易回滚并结束事物
     * 8. 更新原交易为已经预授权完成
     * 9. 成功提交并结束事物
     *
     * @param txnDate          交易日期
     * @param txnTime          交易时间
     * @param txnNum           交易码
     * @param msgSrcId         请求源id
     * @param instId           法人机构号
     * @param traceNo          流水号
     * @param freezeAcctSeqNum 原冻结流水号
     * @param transAmt         交易金额，必须等于原交易金额
     * @param acctNo           账户 (账户 | 客户号 | 外部用户类型&外部用户id)必须至少三选一，若上送则检查是否与原交易一致
     * @param customerId       客户号
     * @param userType         外部用户类型
     * @param userId           外部用户id
     * @param summary          摘要
     * @param rglog            日志句柄
     * @param acctDate         账户日期
     * @param dbsUtil          数据库操作句柄，由外部控制事物
     * @return AcctDetail 成功返回登记的账户交易明细实体
     * @throws Exception 失败抛出异常
     */
    public AcctDetail freezeComplete(String txnDate, String txnTime, String txnNum, String msgSrcId,
                                     String instId, String traceNo, String freezeAcctSeqNum, String transAmt,
                                     String acctNo, String customerId, String userType, String userId,
                                     String summary, RgLogger rglog, String acctDate, DbsUtil dbsUtil) throws Exception {
        return freezeComplete(txnDate, txnTime, txnNum, msgSrcId, instId, traceNo, freezeAcctSeqNum, transAmt,
                acctNo, customerId, userType, userId, summary, rglog, acctDate, dbsUtil, 0);
    }

    /**
     * 普通借贷记操作
     * 1. 检查日切，日切状态为日切结束
     * 2. 开启事物
     * 3. 锁定账户，根据acctNo | customerId | (userType & userId)
     * 4. 检查账户状态、账户可用余额
     * 5. 计算借记或者贷记后账户余额、可用余额并更新
     * 6. 插入记账明细表
     * 7. 再一次检查日切，如果日切状态是否变更为日切开始或者账务日期与原日期不一致则拒绝接交易回滚并结束事物
     * 8. 成功提交并结束事物
     *
     * @param txnDate           交易日期
     * @param txnTime           交易时间
     * @param txnNum            交易码
     * @param msgSrcId          请求源id
     * @param instId            法人机构号
     * @param traceNo           流水号
     * @param transAmt          交易金额
     * @param creditOrDebitFlag 借贷记标志
     * @param acctNo            账户 (账户 | 客户号 | 外部用户类型&外部用户id)必须至少三选一，若上送则检查是否与数据库
     * @param customerId        客户号
     * @param userType          外部用户类型
     * @param userId            外部用户id
     * @param summary           摘要
     * @param rglog             日志句柄
     * @return AcctDetail 成功返回登记的账户交易明细实体
     * @throws Exception 失败抛出异常
     */
    public AcctDetail normal(String txnDate, String txnTime, String txnNum, String msgSrcId, String instId, String traceNo,
                             String transAmt, String creditOrDebitFlag, String acctNo, String customerId,
                             String userType, String userId, String summary, RgLogger rglog) throws Exception {
        //1. 开启事物
        AcctDetail acctDetail;
        String dbPoolName = MapperCommon.getDbPoolName();
        DbsUtil dbsUtil = new DbsUtil(dbPoolName);
        dbsUtil.dbsBeginTransaction();
        try {
            //2. 调用无事物，不提交不回滚普通借贷记操作
            acctDetail = normal(txnDate, txnTime, txnNum, msgSrcId, instId, traceNo,
                    transAmt, creditOrDebitFlag, acctNo, customerId,
                    userType, userId, summary, dbsUtil, rglog);
        } catch (Exception e) {
            dbsUtil.dbsEndTransaction(false);
            throw e;
        }
        //3. 成功提交并结束事物
        dbsUtil.dbsEndTransaction(true);
        return acctDetail;
    }

    /**
     * 普通借贷记操作，不带事物，不提交，不回滚,用于多商户交易，存储总交易笔数
     * 1. 检查日切，日切状态为日切结束
     * 2. 锁定账户，根据acctNo | customerId | (userType & userId)
     * 3. 检查账户状态、账户可用余额
     * 4. 计算借记或者贷记后账户余额、可用余额并更新
     * 5. 插入记账明细表
     * 6. 再一次检查日切，如果日切状态是否变更为日切开始或者账务日期与原日期不一致则拒绝接交易回滚并结束事物
     *
     * @param txnDate           交易日期
     * @param txnTime           交易时间
     * @param txnNum            交易码
     * @param msgSrcId          请求源id
     * @param instId            法人机构号
     * @param traceNo           流水号
     * @param transAmt          交易金额
     * @param creditOrDebitFlag 借贷记标志
     * @param acctNo            账户 (账户 | 客户号 | 外部用户类型&外部用户id)必须至少三选一，若上送则检查是否与数据库
     * @param customerId        客户号
     * @param userType          外部用户类型
     * @param userId            外部用户id
     * @param summary           摘要
     * @param dbsUtil           数据库操作句柄
     * @param rglog             日志句柄
     * @return AcctDetail 成功返回登记的账户交易明细实体
     * @throws BizException 失败抛出异常
     */
    public AcctDetail normal(String txnDate, String txnTime, String txnNum, String msgSrcId, String instId, String traceNo,
                             String transAmt, String creditOrDebitFlag,
                             String acctNo, String customerId, String userType, String userId,
                             String summary, DbsUtil dbsUtil, RgLogger rglog, int total) throws BizException {
        if (StringUtil.isNullorEmpty(traceNo) || StringUtil.isNullorEmpty(instId) ||
                StringUtil.isNullorEmpty(creditOrDebitFlag)) {
            rglog.error("普通借贷记:instId、traceNo、借贷标志必须上送");
            throw new BizException(AcctRespCodeEnum.MSG_VALID_CHECK_ERROR.getRespCode(),
                    AcctRespCodeEnum.MSG_VALID_CHECK_ERROR.getRespDesc());
        }
        if (!AcctCommonConstant.CREDIT.equals(creditOrDebitFlag)) {
            //借贷记标志不合法
            rglog.error("目前只允许进行普通贷记操作，借记操作需要通过预授权类完成后:{}", creditOrDebitFlag);
            throw new BizException(AcctRespCodeEnum.MSG_VALID_CHECK_ERROR.getRespCode(),
                    AcctRespCodeEnum.MSG_VALID_CHECK_ERROR.getRespDesc());
        }
        CheckAcct3o1.checkReq(acctNo, customerId, userType, userId, transAmt, rglog);
        //1. 检查日切，日切状态为日切结束
        //交易金额一定要大于0; (==1交易金额大于0) (==0交易金额等于0) (==-1交易金额小于0)
        if (1 != AmountUtil.compare(transAmt, AcctCommonConstant.ZERO_AMT)) {
            rglog.error("贷记失败:交易金额非法，小于等于0");
            throw new BizException(AcctRespCodeEnum.MSG_VALID_CHECK_ERROR.getRespCode(),
                    AcctRespCodeEnum.MSG_VALID_CHECK_ERROR.getRespDesc());
        }
        //检查记账日期
        String acctDate = CheckCutDayUtil.check(instId, dbsUtil);
        AcctDetail acctDetail;
        //2. 锁定账户，根据acctNo | customerId | (userType & userId)
        AcctInfo acctInfo = QueryAcct3o1.lockAcctInfo(instId, acctNo, customerId, userType, userId, dbsUtil, rglog);
        //3. 检查账户状态、账户可用余额
        //检查账户状态
        if (!AcctStatusEnum.NORMAL.getAcctStatus().equals(acctInfo.getAcctStatus())) {
            rglog.error("记账失败:当前账户状态<{}>不允许借记或者贷记！", acctInfo.getAcctStatus());
            throw new BizException(AcctRespCodeEnum.STATUS_NOT_ALLOW_ERROR.getRespCode(),
                    AcctRespCodeEnum.STATUS_NOT_ALLOW_ERROR.getRespDesc());
        }
        //检查账户余额=可用余额+冻结金额
        if (0 != AmountUtil.compare(acctInfo.getAcctBalance(),
                AmountUtil.addition(acctInfo.getAvailableBalance(), acctInfo.getFreezeBalance()))) {
            //检查账户余额≠可用余额+冻结金额
            throw new BizException(AcctRespCodeEnum.STATUS_NOT_ALLOW_ERROR.getRespCode(),
                    AcctRespCodeEnum.STATUS_NOT_ALLOW_ERROR.getRespDesc());
        }
        //4. 计算借贷记后账户余额、可用余额并更新账户
        String balance;
        String availableBalance;
        //目前商户记账场景不需要普通的借，只能通过预授权类交易完成
        //贷记
        //账户余额增加
        balance = AmountUtil.addition(acctInfo.getAcctBalance(), transAmt);
        //可用余额增加
        availableBalance = AmountUtil.addition(acctInfo.getAvailableBalance(), transAmt);
        rglog.info("交易前余额<{}>可用余额<{}> 交易金额<{}> 交易后余额<{}>可用余额<{}>", acctInfo.getAcctBalance(),
                acctInfo.getAvailableBalance(), transAmt, balance, availableBalance);
        AcctInfoMapper acctInfoMapper = new AcctInfoMapperImpl();
        int retCode = acctInfoMapper.updAcctBalByAcctNo(instId, acctInfo.getAcctNo(), balance, availableBalance, dbsUtil);
        if (Integer.MIN_VALUE == retCode) {
            throw new BizException(AcctRespCodeEnum.DB_EXCEPTION.getRespCode(),
                    AcctRespCodeEnum.DB_EXCEPTION.getRespDesc());
        } else if (0 != retCode) {
            throw new BizException(AcctRespCodeEnum.DB_ERROR.getRespCode(),
                    AcctRespCodeEnum.DB_ERROR.getRespDesc());
        } else {
            //5. 插入记账明细表
            acctDetail = PackAcctDetail.packAcctDetail(txnDate, txnTime, txnNum, msgSrcId, traceNo, summary, transAmt, acctInfo,
                    acctDate, balance, availableBalance, acctInfo.getFreezeBalance(), creditOrDebitFlag, dbsUtil, total);
            AcctDetailMapper acctDetailMapper = new AcctDetailMapperImpl();
            retCode = acctDetailMapper.insert2AcctDetail(acctDetail, dbsUtil);
            if (Integer.MIN_VALUE == retCode) {
                throw new BizException(AcctRespCodeEnum.DB_EXCEPTION.getRespCode(),
                        AcctRespCodeEnum.DB_EXCEPTION.getRespDesc());
            } else if (0 != retCode) {
                throw new BizException(AcctRespCodeEnum.DB_ERROR.getRespCode(),
                        AcctRespCodeEnum.DB_ERROR.getRespDesc());
            } else {
                //6. 再一次检查日切，如果日切状态变更为日切开始或者账务日期与原日期不一致则拒绝交易回滚并结束事物
                acctDateConfirm(instId, acctDate, dbsUtil, rglog);
                return acctDetail;
            }
        }
    }

    /**
     * 普通借贷记操作，不带事物，不提交，不回滚
     * 1. 检查日切，日切状态为日切结束
     * 2. 锁定账户，根据acctNo | customerId | (userType & userId)
     * 3. 检查账户状态、账户可用余额
     * 4. 计算借记或者贷记后账户余额、可用余额并更新
     * 5. 插入记账明细表
     * 6. 再一次检查日切，如果日切状态是否变更为日切开始或者账务日期与原日期不一致则拒绝接交易回滚并结束事物
     *
     * @param txnDate           交易日期
     * @param txnTime           交易时间
     * @param txnNum            交易码
     * @param msgSrcId          请求源id
     * @param instId            法人机构号
     * @param traceNo           流水号
     * @param transAmt          交易金额
     * @param creditOrDebitFlag 借贷记标志
     * @param acctNo            账户 (账户 | 客户号 | 外部用户类型&外部用户id)必须至少三选一，若上送则检查是否与数据库
     * @param customerId        客户号
     * @param userType          外部用户类型
     * @param userId            外部用户id
     * @param summary           摘要
     * @param dbsUtil           数据库操作句柄
     * @param rglog             日志句柄
     * @return AcctDetail 成功返回登记的账户交易明细实体
     * @throws BizException 失败抛出异常
     */
    public AcctDetail normal(String txnDate, String txnTime, String txnNum, String msgSrcId, String instId, String traceNo,
                             String transAmt, String creditOrDebitFlag,
                             String acctNo, String customerId, String userType, String userId,
                             String summary, DbsUtil dbsUtil, RgLogger rglog) throws BizException {
        return normal(txnDate, txnTime, txnNum, msgSrcId, instId, traceNo, transAmt, creditOrDebitFlag,
                acctNo, customerId, userType, userId, summary, dbsUtil, rglog, 0);
    }

    /**
     * 提交时再次确认账务日期
     *
     * @param instId       法人机构号
     * @param origAcctDate 交易进入时的账务日期
     * @param rglog        日志句柄
     * @throws BizException 不成功都抛出异常
     */
    public void acctDateConfirm(String instId, String origAcctDate, DbsUtil dbsUtil, RgLogger rglog) throws BizException {
        try {
            String acctDateConfirm = CheckCutDayUtil.check(instId, dbsUtil);
            if (StringUtil.isNullorEmpty(acctDateConfirm)) {
                rglog.error("交易进入时未日切，交易提交前已经开始日切");
                throw new BizException("交易进入时未日切，交易提交前已经开始日切");
            } else {
                if (!acctDateConfirm.equals(origAcctDate)) {
                    //oracle数据库下不存在此种情况，因为交易时锁了账户数据，日切也锁了账户，同时只能上一个锁
                    rglog.error("交易进入时未日切，交易提交前日切结束");
                    throw new BizException("交易进入时未日切，交易提交前日切结束");
                }
            }
        } catch (Exception e) {
            rglog.error(StringUtil.ExceptionToString(e));
            throw e;
        }
    }
}
